package paodekuai

import (
	"fmt"
	"gitee.com/yuya000/cards-game-server-go/logwarp"
	"gitee.com/yuya000/cards-game-server-go/server"
	"github.com/garyburd/redigo/redis"
	"github.com/shopspring/decimal"
	"github.com/xormplus/xorm"
	"math"
	"math/rand"
	"sync"
)

const (
	StatWait    = iota // 等待 0
	StatReady          // 准备 1
	StatStart          // 游戏开始 2
	StatFaPai          // 发牌 3
	StatChuPai         // 出牌 4
	StatJieSuan        // 结算 5
	StatVote           // 游戏解散投票 6
)

type Paodekuai struct {
	gameOver       bool
	stat           int
	tempStat       int
	mutex          sync.Mutex
	id             string
	name           string
	players        *server.PlayerMap
	readyPlayers   *server.PlayerMap
	conf           *PaoDeKuaiConf
	args           server.Args
	sequence       []*server.Player
	owner          *server.Player
	currentPlayers int
	holdPais       map[string]map[string]bool
	firster        int
	currentRing    int
	currentRound   int
	currentPais    *Pai
	lastPai        *Pai
	bombHoldPlayer *server.Player
	previousWinner *server.Player
	winner         *server.Player
	chupaiPlayer   *server.Player
	bombs          map[string]int
	bombsEntry     map[string]int
	ballotBox      map[string]string
	mustPut        bool
}

func (p *Paodekuai) Id() string { return p.id }

func (p *Paodekuai) Name() string { return p.name }

func (p *Paodekuai) genSeq(player *server.Player) int {
	for seq, pl := range p.sequence {
		if pl == nil {
			return seq
		}
	}
	return -1
}

func (p *Paodekuai) JoinGame(player *server.Player) server.Exception {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	if p.stat >= StatStart || p.currentRing > 1 {
		return GameStartException
	}
	if p.players.Len() >= p.conf.maxPlayers {
		return PlayerFullException
	}
	// 如果是平摊需要检测加入的玩家的玉令数
	if p.conf.payment == 1 && player.CurrentClub.HouseCards < p.conf.cost {
		return server.CostDeficiencyException
	}
	seq := p.genSeq(player)
	if seq == -1 {
		return server.SystemException
	}
	// 发送游戏类型
	player.Unicast(&server.GameTypeResp{GameType: p.Name()}, server.MessageTypeGameType)
	p.Broadcast(player, MessageTypePlayerJoinEvent, &PlayerJoinNotice{
		Seq:      seq,
		UserInfo: player.UserInfo.ToUserInfoEntry(),
		UserClub: player.CurrentClub.ToClubInfoEntry(),
	})
	p.sequence[seq] = player
	var sequence []string
	for _, pl := range p.sequence {
		if pl != nil {
			sequence = append(sequence, pl.Id)
		}
	}
	player.Unicast(&JoinGameResp{
		Seq:           seq,
		GameType:      p.Name(),
		HouseNo:       p.Id(),
		Players:       p.players.ToUsers(),
		PlayerClubs:   p.players.ToClubs(),
		GameBuildArgs: p.args,
		ReadyPlayers:  p.readyPlayers.Keys(),
		Sequence:      sequence,
	}, server.MessageTypeJoinGame)
	p.players.Store(player.Id, player)
	return nil
}

func (p *Paodekuai) ResumeGame(aPlayer *server.Player) server.Exception {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	player, exist := p.players.Load(aPlayer.Id)
	if !exist {
		return server.InvalidOptException
	}
	player.Session = aPlayer.Session
	player.UserInfo = aPlayer.UserInfo
	player.CurrentClub = aPlayer.CurrentClub
	player.Stat = server.PlayerStatNomal
	if p.currentRing <= 1 {
		if p.conf.payment == 1 && player.CurrentClub.HouseCards < p.conf.cost {
			return server.CostDeficiencyException
		}
	}
	var playerHoldingPai []string
	for pai, _ := range p.holdPais[player.Id] {
		playerHoldingPai = append(playerHoldingPai, pai)
	}
	var playersClubInfos = make(map[string]*server.ClubInfoEntry)
	p.players.Foreach(func(id string, player *server.Player) {
		playersClubInfos[id] = player.CurrentClub.ToClubInfoEntry()
	})
	var lostPlayer []string
	p.players.Foreach(func(id string, player *server.Player) {
		if player.Stat == server.PlayerLost {
			lostPlayer = append(lostPlayer, id)
		}
	})
	var otherPlayerPais = make(map[string]int)
	for id, pai := range p.holdPais {
		if id == player.Id {
			continue
		}
		otherPlayerPais[id] = len(pai)
	}
	var (
		lastPai      []string
		lastPaiStyle int
	)
	if p.lastPai != nil {
		lastPai = p.lastPai.Pai
		lastPaiStyle = p.lastPai.Style
	} else {
		lastPaiStyle = PaiStyleEmpty
	}
	var sequence []string
	var selfSeq int
	for i, seq := range p.sequence {
		if seq != nil {
			sequence = append(sequence, seq.Id)
			if seq.Eq(player) {
				selfSeq = i
			}
		}
	}
	player.Unicast(&PlayerResumeEvent{
		GameType:            p.Name(),
		No:                  p.Id(),
		Status:              p.stat,
		Rules:               p.args,
		Owner:               p.owner.Id,
		MaxRound:            p.conf.maxRound,
		CurrentRound:        p.currentRing,
		ReadyPlayer:         p.readyPlayers.Keys(),
		PlayerHoldingPai:    playerHoldingPai,
		GameExitBallotBox:   p.ballotBox,
		Players:             p.players.ToUsers(),
		PlayersClubInfo:     playersClubInfos,
		LostPlayer:          lostPlayer,
		CurrentChuPaiPlayer: p.sequence[p.firster].Id,
		LastPai:             lastPai,
		LastPaiStyle:        lastPaiStyle,
		OtherPlayerPais:     otherPlayerPais,
		Sequence:            sequence,
		SelfSeq:             selfSeq,
	}, server.MessageTypePlayerResume)
	p.Broadcast(player, MessageTypePlayerResume, &PlayerResumeNoticeEvent{Id: player.Id})
	return nil
}

// 游戏开始
func (p *Paodekuai) start(player *server.Player) {
	logwarp.Std().Infoln("start()")
	if !player.Eq(p.owner) {
		logwarp.Std().Warningln("需要房主才能开始游戏")
		return
	}
	if p.players.Len() != p.conf.maxPlayers {
		logwarp.Std().Warningln("进入游戏的玩家 还不够", p.players.Len(), p.conf.maxPlayers)
		return
	}
	p.readyPlayers.Store(p.owner.Id, p.owner)
	if p.readyPlayers.Len() != p.players.Len() {
		logwarp.Std().Warningln("准备好的玩家 还不够", p.readyPlayers.Len(), p.players.Len())
		return
	}
	p.stat = StatFaPai
	p.Broadcast(nil, MessageTypeGameStart, nil)
	p.fapai()
}

// 玩家准备
func (p *Paodekuai) ready(player *server.Player) {
	logwarp.Std().Infoln("ready()")
	if p.currentRing <= 1 && p.stat != StatWait {
		logwarp.Std().Warningln("错误操作", p.stat)
		return
	}
	p.readyPlayers.Store(player.Id, player)
	p.Broadcast(nil, MessageTypeReady, &ReadyNotice{Id: player.Id})
	//	p.stat = StatStart

	if p.currentRing > 1 && p.readyPlayers.Len() == p.players.Len() {
		p.stat = StatFaPai
		p.fapai()
	}
}

func (p *Paodekuai) fapai() {
	logwarp.Std().Infoln("fapai()")
	if p.stat != StatFaPai {
		logwarp.Std().Warningln("错误操作", p.stat)
		return
	}
	pais := RandomGenPai(p.players.Len())
	logwarp.Std().Debugln(pais)
	i := 0
	p.players.Foreach(func(id string, player *server.Player) {
		pai := make(map[string]bool)
		for _, p := range pais[i] {
			pai[p] = true
		}
		p.holdPais[id] = pai
		logwarp.Std().Debugln(pais[i], id)
		player.Unicast(&FaPaiNotice{Pai: pais[i]}, MessageTypeFaPai)
		i++
	})
	p.stat = StatChuPai
	p.currentRound = 1

	if p.currentRing > 1 && p.previousWinner != nil {
		for i, pl := range p.sequence {
			if pl.Eq(p.previousWinner) {
				p.firster = i
			}
		}
		p.previousWinner = nil
	} else if p.players.Len() == 2 { // 两人随机先出
		p.firster = rand.Intn(len(p.sequence))
	} else { // 三人黑桃3先出
		for id, holdPai := range p.holdPais {
			if _, exist := holdPai["hei_3"]; exist {
				for i, pl := range p.sequence {
					if pl.Id == id {
						p.firster = i
						goto end
					}
				}
			}
		}
	}
end:
	//	p.firster = 0
	p.Broadcast(nil, MessageTypeChuPaiNotice, &ChuPaiNotice{Id: p.sequence[p.firster].Id})
}

func (p *Paodekuai) playerExit(player *server.Player) {
	logwarp.Std().Infoln("playerExit")
	if p.currentRing > 1 || p.stat >= StatFaPai {
		logwarp.Std().Infoln("目前不允许退出")
		return
	}
	if p.owner.Eq(player) { // 房主退出游戏解散
		p.Broadcast(player, MessageTypeGameDestroy, nil)
		p.gameOver = true
		p.players.Foreach(func(id string, player *server.Player) {
			player.Destroy()
		})
		return
	}
	p.players.Del(player.Id)
	p.readyPlayers.Del(player.Id)
	for i, seq := range p.sequence {
		if seq.Eq(player) {
			p.sequence[i] = nil
			break
		}
	}
	p.currentPlayers--
	player.Destroy()
	p.Broadcast(nil, MessageTypeExit, &ExitNotice{Id: player.Id})
}

func (p *Paodekuai) voteDissolve(player *server.Player) {
	if p.stat == StatVote {
		return
	}
	p.tempStat = p.stat
	p.stat = StatVote
	p.Broadcast(nil, MessageTypeVoteDissolve, &VoteNotice{Id: player.Id})
	p.ballotBox[player.Id] = "yes"
}

func (p *Paodekuai) vote(player *server.Player, msg *server.Message) {
	logwarp.Std().Infoln("vote")
	var vote VoteReq
	if err := server.MsgBodyParser(msg.Data, &vote); err != nil {
		logwarp.Std().Warningln(err)
		return
	}
	logwarp.Std().Debugln("vote", vote)
	if _, exist := p.ballotBox[player.Id]; exist {
		logwarp.Std().Warningln("已经投过票")
		return
	}
	if vote.YesOrNo != "yes" && vote.YesOrNo != "no" {
		logwarp.Std().Warningln(vote.YesOrNo)
		return
	}
	p.ballotBox[player.Id] = vote.YesOrNo
	p.Broadcast(nil, MessageTypeVote, &VoteResp{
		Id:      player.Id,
		YesOrNo: vote.YesOrNo,
	})
	var (
		yesN int
		NoN  int
	)
	for _, yesOrNo := range p.ballotBox {
		switch yesOrNo {
		case "yes":
			yesN++
		case "no":
			NoN++
		}
	}
	if yesN == p.players.Len() {
		// 给其他玩家发送退出消息
		p.gameover()
	} else if yesN+NoN == p.players.Len() { // 重置继续游戏
		p.ballotBox = make(map[string]string)
		p.Broadcast(nil, MessageTypeGameContinue, nil)
		p.stat = p.tempStat
	}
}

func (p *Paodekuai) Play(player *server.Player, msg *server.Message) error {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	switch msg.Header.MessageType {
	case MessageTypeReady:
		p.ready(player)
	case MessageTypeGameStart:
		p.start(player)
	case MessageTypeChuPai:
		p.chupai(player, msg)
	case MessageTypeChuPaiCheck:
		p.chupaiCheck(player, msg)
	case MessageTypeExit:
		p.playerExit(player)
	case MessageTypeVoteDissolve:
		p.voteDissolve(player)
	case MessageTypeVote:
		p.vote(player, msg)
	case MessageTypePosition:
		p.position(player, msg)
	default:
		logwarp.Std().Warningln("未知消息类型", msg.Header.MessageType)
	}
	return nil
}

func (p *Paodekuai) position(player *server.Player, msg *server.Message) {
	logwarp.Std().Debugln("position")
	var positionReq PositionReq
	if err := server.MsgBodyParser(msg.Data, &positionReq); err != nil {
		logwarp.Std().Warningln(err)
		return
	}
	logwarp.Std().Debugln(positionReq)
	if positionReq.Id == "" {
		return
	}
	for i, p := range p.sequence {
		if p.Id == positionReq.Id {
			logwarp.Std().Debugln("position", i, p)
			player.Unicast(&PositionResp{
				Position: i,
			}, MessageTypePosition)
			break
		}
	}
}

func (p *Paodekuai) chupaiCheck(player *server.Player, msg *server.Message) {
	var chuPaiCheckReq ChuPaiCheckReq
	if err := server.MsgBodyParser(msg.Data, &chuPaiCheckReq); err != nil {
		logwarp.Std().Warningln(err)
		return
	}
	holdPais := p.holdPais[player.Id]
	var holdPaisSlice []string
	for pai, _ := range holdPais {
		holdPaisSlice = append(holdPaisSlice, pai)
	}
	logwarp.Std().Debugln("发送的牌", chuPaiCheckReq.Pai, holdPaisSlice)
	pai := NewPai(chuPaiCheckReq.Pai, holdPaisSlice)
	logwarp.Std().Debugln("牌", pai.Pai)
	logwarp.Std().Debugln("牌点", pai.Paival)
	logwarp.Std().Debugln("牌型", PaiPointText(pai.Style))
	logwarp.Std().Debugln("点数", pai.Point)
	player.Unicast(&ChuPaiCheckResp{
		Style: pai.Style,
	}, MessageTypeChuPaiCheck)
}

func (p *Paodekuai) peekNextRoundPlayer() *server.Player {
	if p.firster == len(p.sequence)-1 {
		return p.sequence[0]
	}
	return p.sequence[p.firster+1]
}

func (p *Paodekuai) nextRound() {
	defer func() {
		p.currentRound++
		p.Broadcast(nil, MessageTypeChuPaiNotice, &ChuPaiNotice{Id: p.sequence[p.firster].Id})
	}()
	if p.firster == len(p.sequence)-1 {
		p.firster = 0
		return
	}
	p.firster++
}

func (p *Paodekuai) nextRing() {
	logwarp.Std().Infoln("下一轮")
	p.holdPais = make(map[string]map[string]bool)
	for i, seq := range p.sequence {
		if seq.Eq(p.winner) {
			p.firster = i
		}
	}
	p.stat = StatWait
	p.readyPlayers = server.NewPlayerMap()
	p.currentRound = 0
	p.currentPais = nil
	p.lastPai = nil
	p.bombHoldPlayer = nil
	p.bombsEntry = make(map[string]int)
	p.previousWinner = p.winner
	p.winner = nil
	p.ballotBox = make(map[string]string)
	p.players.Foreach(func(id string, player *server.Player) {
		player.NextRound()
	})
	p.currentRing++
	p.Broadcast(nil, MessageTypeNextRound, nil)
}

func (p *Paodekuai) mustPutHei3(pai []string) bool {
	if p.currentRing == 1 && p.conf.mustPut && p.players.Len() == 3 && !p.mustPut {
		for _, chupai := range pai {
			if chupai == "hei_3" {
				p.mustPut = true
				return true
			}
		}
		logwarp.Std().Warningln("没有第一局出黑桃三")
		return false
	}
	return true
}

func (p *Paodekuai) paiValid(player *server.Player, chupai []string) bool {
	holdPais := p.holdPais[player.Id]
	for _, chupai := range chupai {
		if _, exist := holdPais[chupai]; !exist {
			logwarp.Std().Warningln("非法的牌", chupai)
			return false
		}
	}
	return true
}

func (p *Paodekuai) currentPlayer() *server.Player {
	return p.sequence[p.firster]
}

func (p *Paodekuai) bombsRecord(player *server.Player, n int) {
	if _, exist := p.bombs[player.Id]; exist {
		p.bombsEntry[player.Id] += n
	} else {
		p.bombsEntry[player.Id] = n
	}
}

func (p *Paodekuai) bombsInc(player *server.Player) {
	if _, exist := p.bombs[player.Id]; exist {
		p.bombs[player.Id] += 1
	} else {
		p.bombs[player.Id] = 1
	}
}

func (p *Paodekuai) getPlayerBombs(player *server.Player) int {
	if _, exist := p.bombs[player.Id]; exist {
		return p.bombs[player.Id]
	}
	return 0
}

func (p *Paodekuai) chupai(player *server.Player, msg *server.Message) {
	logwarp.Std().Infoln("chuPai()")
	if p.stat != StatChuPai {
		logwarp.Std().Warningln("错误操作", p.stat)
		return
	}
	// 判断玩家身份
	if !player.Eq(p.currentPlayer()) {
		logwarp.Std().Warningln("你并不是当前应该出牌的人", player, p.sequence[p.firster])
		return
	}
	var chuPaiReq ChuPaiReq
	if err := server.MsgBodyParser(msg.Data, &chuPaiReq); err != nil {
		logwarp.Std().Warningln(err)
		return
	}
	if p.currentRound == 1 && len(chuPaiReq.Pai) == 0 {
		logwarp.Std().Warningln("非法的牌 第一次出牌必须出", chuPaiReq.Pai)
		return
	}
	// 判断牌的合法性
	if !p.paiValid(player, chuPaiReq.Pai) {
		logwarp.Std().Warningln("非法的牌", chuPaiReq.Pai)
		return
	}
	// 判断黑桃三必出
	if !p.mustPutHei3(chuPaiReq.Pai) {
		logwarp.Std().Warningln("必出黑桃三 没有出")
		return
	}
	holdPais := p.holdPais[player.Id]
	var holdPaisSlice []string
	for pai, _ := range p.holdPais[player.Id] {
		holdPaisSlice = append(holdPaisSlice, pai)
	}
	pai := NewPai(chuPaiReq.Pai, holdPaisSlice)
	if pai.Style == PaiStyleUnknow {
		logwarp.Std().Warningln("未知的牌型", chuPaiReq.Pai)
		return
	}
	p.lastPai = pai
	logwarp.Std().Debugln("牌型", PaiPointText(pai.Style))
	logwarp.Std().Debugln("点数", pai.Point)
	if pai.Style == PaiStyleEmpty {
		if p.peekNextRoundPlayer().Eq(p.chupaiPlayer) {
			p.currentPais = nil
			logwarp.Std().Warningln("重建牌型")
		}
		logwarp.Std().Warningln("要不起", chuPaiReq.Pai)
		if p.currentPais == nil {
			p.Broadcast(nil, MessageTypeChuPai, &ChuPaiResp{
				Id:              player.Id,
				CurrentPai:      []string{},
				CurrentPaiStyle: PaiStyleEmpty,
				ChuPai:          chuPaiReq.Pai,
			})
		} else {
			p.Broadcast(nil, MessageTypeChuPai, &ChuPaiResp{
				Id:              player.Id,
				CurrentPai:      p.currentPais.Pai,
				CurrentPaiStyle: p.currentPais.Style,
				ChuPai:          chuPaiReq.Pai,
			})
		}
		p.nextRound()
	} else {
		if pai.Style == PaiStyleBomb && p.bombHoldPlayer == nil { // 有人出了炸弹
			p.bombHoldPlayer = player
			p.bombsInc(player)
			p.bombsRecord(player, 1)
		} else if p.currentPais != nil { // 上一家出的牌
			if p.currentPais.Style != pai.Style {
				logwarp.Std().Warningln("牌型不同", p.currentPais.Style, pai.Style)
				logwarp.Std().Warningln(p.currentPais.Pai, pai.Pai)
				player.SendException(PaiStyleException, MessageTypeChuPai)
				return
			}
			// 下一个玩家报单 必须出手牌最大的单张
			if pai.Style == PaiStyleSola && len(p.holdPais[p.peekNextRoundPlayer().Id]) == 1 {
				maxPaiv := PaisMaxVal(pai.Paival)
				if maxPaiv != PaiValMap[chuPaiReq.Pai[0]] {
					logwarp.Std().Warningln("下一个玩家报单 必须出手牌最大的单张", holdPaisSlice, maxPaiv, chuPaiReq)
					return
				}
			}
			if p.currentPais.Point >= pai.Point {
				logwarp.Std().Warningln("牌点数太小", p.currentPais.Point, pai.Point)
				logwarp.Std().Warningln(p.currentPais.Pai, pai.Pai)
				return
			} else if pai.Style == PaiStyleBomb { // 出现了更大的炸弹
				if p.bombHoldPlayer != nil {
					p.bombsRecord(p.bombHoldPlayer, -1)
					p.bombsRecord(player, 1)
				}
				p.bombHoldPlayer = player
				p.bombsInc(player)
			}
		}
		p.currentPais = pai
		p.chupaiPlayer = player
		p.Broadcast(nil, MessageTypeChuPai, &ChuPaiResp{
			Id:              player.Id,
			CurrentPai:      chuPaiReq.Pai,
			CurrentPaiStyle: pai.Style,
			ChuPai:          chuPaiReq.Pai,
		})
		for _, p := range chuPaiReq.Pai {
			delete(holdPais, p)
		}
		if len(holdPais) <= 0 {
			logwarp.Std().Infoln("牌出完了胜出", player)
			p.winner = player
			p.stat = StatJieSuan
			p.jiesuan()
		} else {
			p.nextRound()
		}
	}
}

func (p *Paodekuai) jiesuan() {
	if p.stat != StatJieSuan {
		logwarp.Std().Warningln("当前状态不对", p.stat)
		return
	}
	if p.winner == nil {
		p.winner = p.peekNextRoundPlayer()
		logwarp.Std().Warningln("并没有人获胜", p.winner)
	}
	var (
		winScore int
	)
	logwarp.Std().Debugln("炸弹记录", p.bombsEntry)
	logwarp.Std().Debugln("手牌", p.holdPais)
	for id, n := range p.bombsEntry {
		if n <= 0 {
			continue
		}
		for i := 0; i < n; i++ {
			p.players.Foreach(func(oid string, player *server.Player) {
				if oid == id {
					return
				}
				player.AddScore(10 * -1)
				winScore += 10
			})
		}
		if winPlayer, exist := p.players.Load(id); exist {
			winPlayer.AddScore(winScore)
		}
		winScore = 0
	}
	winScore = 0
	p.players.Foreach(func(id string, player *server.Player) {
		if player.Eq(p.winner) {
			return
		}
		holdPai := p.holdPais[id]
		if len(holdPai) == 1 {
		} else if len(holdPai) == 16 { // 被关
			winScore += 32
			player.AddScore(-32)
		} else {
			winScore += len(holdPai)
			player.AddScore(len(holdPai) * -1)
		}
	})
	logwarp.Std().Debugln("玩家赢得", winScore)
	p.winner.AddScore(winScore)
	p.winner.Win()

	var jieSuanEntrys []*JieSuanEntry
	p.players.Foreach(func(id string, player *server.Player) {
		logwarp.Std().Debugln("小结算", JieSuanEntry{
			Id:       id,
			HoldPais: len(p.holdPais[id]),
			Score:    player.CurrentScore,
		})
		jieSuanEntrys = append(jieSuanEntrys, &JieSuanEntry{
			Id:       id,
			HoldPais: len(p.holdPais[id]),
			Score:    player.CurrentScore,
		})
	})
	p.Broadcast(nil, MessageTypeJieSuan, &JieSuanResp{Winner: p.winner.Id, JieSuanEntrys: jieSuanEntrys})
	if p.currentRing == 1 { // 扣除玉令
		switch p.conf.payment {
		case 0:
			server.DBEngine.ID(p.owner.CurrentClub.Id).Decr("house_cards", p.conf.cost).Update(&server.CurrentClub{})
			server.DBEngine.ID(p.owner.Id).Incr("cost_house_cards", p.conf.cost).Update(&server.User{})
		case 1:
			p.players.Foreach(func(id string, player *server.Player) {
				server.DBEngine.ID(player.CurrentClub.Id).Decr("house_cards", p.conf.cost).Update(&server.CurrentClub{})
				server.DBEngine.ID(id).Incr("cost_house_cards", p.conf.cost).Update(&server.User{})
			})
		}
	}

	p.UpdateUserData()
	p.UpdateUserRank()

	if p.currentRing >= p.conf.maxRound { // 总结算
		var zongJieSuanEntrys []*ZongJieSuanEntry
		p.players.Foreach(func(id string, player *server.Player) {
			logwarp.Std().Debugln("总结算", ZongJieSuanEntry{
				Id:    id,
				Bombs: p.getPlayerBombs(player),
				Wins:  player.Wins,
				Score: player.RoundOfScore,
			})
			zongJieSuanEntrys = append(zongJieSuanEntrys, &ZongJieSuanEntry{
				Id:    id,
				Bombs: p.getPlayerBombs(player),
				Wins:  player.Wins,
				Score: player.RoundOfScore,
			})
		})
		p.Broadcast(nil, MessageTypeZongJieSuan, &ZongJieSuanResp{ZongJieSuanEntrys: zongJieSuanEntrys})
		p.gameover()
	} else {
		p.nextRing()
	}
}

func (p *Paodekuai) UpdateUserData() {
	var sqlSession *xorm.Session
	p.players.Foreach(func(id string, player *server.Player) {
		sqlSession = server.DBEngine.ID(id)
		switch {
		case player.CurrentScore > 0:
			sqlSession.Incr("score", math.Abs(float64(player.CurrentScore)))
		case player.CurrentScore < 0:
			sqlSession.Decr("score", math.Abs(float64(player.CurrentScore)))
		}
		sqlSession.Incr("games_innings").Incr("paodekuai_innings")
		if player.Wins > 0 {
			sqlSession.Incr("games_win_innings", player.Wins).Incr("paodekuai_win_innings", player.Wins)
		}
		if _, err := sqlSession.Update(&server.User{}); err != nil {
			logwarp.Std().Warningln(err)
		}
	})
	p.players.Foreach(func(id string, player *server.Player) {
		if err := player.UpdateUserInfo(); err != nil {
			logwarp.Std().Warningln(err)
		}
	})
	p.players.Foreach(func(id string, player *server.Player) {
		u := player.UserInfo
		if u.GamesInnings > 0 {
			b := decimal.NewFromFloat(float64(u.GamesInnings))
			a := decimal.NewFromFloat(float64(u.PaoDeKuaiWinInnings))
			v := a.DivRound(b, 2).Mul(decimal.NewFromFloat(100)).IntPart()
			if _, err := server.DBEngine.ID(id).Update(&server.User{PaoDeKuaiWinRate: int(v)}); err != nil {
				logwarp.Std().Warningln(err)
			}
		}
	})
	p.players.Foreach(func(id string, player *server.Player) {
		if err := player.UpdateUserInfo(); err != nil {
			logwarp.Std().Warningln(err)
		}
	})
}

func (p *Paodekuai) UpdateUserRank() {
	logwarp.Std().Debugln("UpdateUserRank")
	redisConn := server.RedisPool.Get()
	p.players.Foreach(func(s string, player *server.Player) {
		key := fmt.Sprintf("%s_%s", "CLUBRANK", player.CurrentClub.ClubId)
		if _, err := redis.Int(redisConn.Do("ZADD", key, player.UserInfo.Score, player.Id)); err != nil {
			logwarp.Std().Warningln(err)
		}
	})
}

func (p *Paodekuai) gameover() {
	logwarp.Std().Infoln("gameover()")
	p.Broadcast(nil, MessageTypeGameover, nil)
	p.gameOver = true
	p.players.Foreach(func(s string, player *server.Player) {
		player.Session.Flush()
		player.Destroy()
	})
}

func (p *Paodekuai) Broadcast(skip *server.Player, mt uint16, data interface{}) error {
	p.players.Foreach(func(s string, player *server.Player) {
		if player.Eq(skip) {
			return
		}
		if err := player.Unicast(data, mt); err != nil {
			logwarp.Std().Warningln(err)
		}
		player.Session.Flush()
	})
	return nil
}

func (p *Paodekuai) Players() []*server.Player {
	var retval []*server.Player
	p.players.Foreach(func(s string, player *server.Player) {
		retval = append(retval, player)
	})
	return retval
}

func (p *Paodekuai) GameOver() bool {
	return p.gameOver
}
func (p *Paodekuai) LivePlayersN() int { return p.players.Len() }

func (p *Paodekuai) GetOwner() *server.Player { return p.owner }
